#pragma once

#include "Math/Math.h"
#include <vector>
#include <string>
#include <set>

const float GRAVITY = 8.0f;
const float BOX_SIZE = 12.0f;

const float TIME_BETWEEN_UPDATES = 0.01f;
const int TIMER_MS = 25;

const int MAX_OCTREE_DEPTH = 6;
const int MIN_BALLS_PER_OCTREE = 3;
const int MAX_BALLS_PER_OCTREE = 6;

struct Ball
{
	glm::vec3 velocity;
	glm::vec3 position;
	float radius;
	glm::vec3 color;
};

enum Wall
{
	WALL_LEFT,
	WALL_RIGHT,
	WALL_FAR,
	WALL_NEAR,
	WALL_TOP,
	WALL_BOTTOM
};

struct BallPair
{
	Ball* ball1;
	Ball* ball2;
};

struct BallWallPair
{
	Ball* ball;
	Wall wall;
};

class Octree
{
private:
	glm::vec3 mAA; 
	glm::vec3 mBB; 
	glm::vec3 mCenter;  

	Octree* mChildren[2][2][2];
	//children[0][*][*] are the children with x coordinates ranging from minX to centerX
	//children[1][*][*] are the children with x coordinates ranging from centerX to maxX.
	//similarly for the other two dimensions of this array

	bool mHasChildren;
	std::set<Ball*> mBalls;
	int mDepth;
	int mBallNum;

	//Adds a ball to or removes one from the children of this
	void FileBall(Ball* ball, glm::vec3 pos, bool addBall);

	//Creates children of this, and moves the balls in this to the children
	void CreateChildren();

	//Destroys the children of this, and moves all balls in its descendants
	//to the "balls" set
	void DestroyChildren();

	//Adds all balls in this or one of its descendants to the specified set
	void CollectBalls(std::set<Ball*>& bs);

	//Removes the specified ball at the indicated position
	void Remove(Ball* ball, glm::vec3 pos);

	void PotentialBallWallCollisions(std::vector<BallWallPair>& cs, Wall w, char coord, int dir);

public:
	//c1(minX, minY, minZ)
	//c2(maxX, maxY, maxZ)
	Octree(glm::vec3 c1, glm::vec3 c2, int32 depth);

	~Octree();

	//Adds a ball to this
	void Add(Ball* ball);

	//Removes a ball from this
	void Remove(Ball* ball);

	//Changes the position of a ball in this from oldPos to ball->pos
	void MoveBall(Ball* ball, glm::vec3 oldPos);

	//Adds potential ball-wall collisions to the specified set
	void PotentialBallWallCollisions(std::vector<BallWallPair>& collisions);

	//Adds potential ball-ball collisions to the specified set
	void PotentialBallBallCollisions(std::vector<BallPair>& collisions);
};
void PotentialBallBallCollisions(std::vector<BallPair>& potentialCollisions,
	std::vector<Ball*>& balls, Octree* octree);
void PotentialBallWallCollisions(std::vector<BallWallPair>& potentialCollisions,
	std::vector<Ball*>& balls, Octree* octree);

//Moves all of the balls by their velocity times dt
void MoveBalls(std::vector<Ball*>& balls, Octree* octree, float dt);

//Decreases the y coordinate of the velocity of each ball by GRAVITY *
//TIME_BETWEEN_UPDATES
void ApplyGravity(std::vector<Ball*>& balls);


//Returns whether two balls are colliding
bool TestBallBallCollision(Ball* b1, Ball* b2);

//Handles all ball-ball collisions
void HandleBallBallCollisions(std::vector<Ball*>& balls, Octree* octree);


//Returns the direction from the origin to the wall
glm::vec3 WallDirection(Wall wall);

//Returns whether a ball and a wall are colliding
bool TestBallWallCollision(Ball* ball, Wall wall);

//Handles all ball-wall collisions
void HandleBallWallCollisions(std::vector<Ball*>& balls, Octree* octree);

//Applies gravity and handles all collisions.  Should be called every
//TIME_BETWEEN_UPDATES seconds.
void PerformUpdate(std::vector<Ball*>& balls, Octree* octree);

//Advances the state of the balls by t.  timeUntilUpdate is the amount of time
//until the next call to performUpdate.
void Advance(std::vector<Ball*>& balls, Octree* octree, float t, float& timeUntilUpdate);

